package controller;

import eccezioni.AnnuncioChiusoException;
import eccezioni.CampoErratoException;
import eccezioni.UtenteNonCreatoreException;
import entity.Adesione;
import entity.Annuncio;
import entity.Articolo;
import entity.IndirizzoAbitazione;
import entity.SitoAcquisti;
import entity.UtenteRegistrato;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import sbForEntityClasses.AdesioneFacadeLocal;
import sbForEntityClasses.AnnuncioFacadeLocal;
import sbForEntityClasses.ArticoloFacadeLocal;
import sbForEntityClasses.IndirizzoAbitazioneFacadeLocal;
import sbForEntityClasses.SitoAcquistiFacadeLocal;
import sbForEntityClasses.UtenteRegistratoFacadeLocal;
import utilities.Regexp;
import utilities.googleMap.DistanceMatrix;

/**
 * Metodi per il creatore dell'annuncio
 * @author Ale
 */
@Stateless
public class ControllerCreazioneAnnuncio implements ControllerCreazioneAnnuncioLocal {

    @EJB
    private ControllerAmministrazioneLocal controllerAmministrazione;
    @EJB
    private ArticoloFacadeLocal articoloFacade;
    @EJB
    private UtenteRegistratoFacadeLocal utenteRegistratoFacade;
    @EJB
    private SitoAcquistiFacadeLocal sitoAcquistiFacade;
    @EJB
    private ControllerGMapsLocal controllerGMaps;
    @EJB
    private AdesioneFacadeLocal adesioneFacade;
    @EJB
    private AnnuncioFacadeLocal annuncioFacade;
    @EJB
    private IndirizzoAbitazioneFacadeLocal indirizzoAbitazioneFacade;
    @EJB
    private ControllerComuniLocal controllerComuni;
    @PersistenceContext(unitName = "ShippingShareBisBis-ejbPU")
    private EntityManager em;

    /**
     * Metodo per inserire un annuncio
     * @param sito url del sito a cui l'annuncio si riferisce
     * @param chiusuraStr data di chiusura, nel formato dd-MM-yyyy
     * @param ordineStr data di ordine, nel formato dd-MM-yyyy
     * @param spedizioneStr spese di spedizione
     * @param indirizzo indirizzo a cui gli iscritti all'annuncio potranno ritirare i propri articoli
     * @param gratuitaStr "on" sse il sito prevede la spedizione gratuita a fronte di una certa spesa minima
     * @param minimaGratuitaStr spesa minima per ottenere la spedizione gratuita, può essere null se gratuitaStr non è "on"
     * @param spesaInizialeStr spesa iniziale del creatore dell'annuncio, può essere null se gratuitaStr non è "on"
     * @param creatore utente che crea l'annuncio
     * @throws CampoErratoException se uno o più campi sono errati
     */
    @Override
    public void inserisciAnnuncio(String sito, String chiusuraStr, String ordineStr, String spedizioneStr, String indirizzo, String gratuitaStr, String minimaGratuitaStr, String spesaInizialeStr, UtenteRegistrato creatore) throws CampoErratoException {
        //controlli lato server
        String errori = "";
        if (!Regexp.checkURL(sito, true)) {
            errori += "<li>" + sito + " non &egrave; un url valido</li>";
        }

        DateFormat dataP = new SimpleDateFormat("dd-MM-yyyy");

        GregorianCalendar ordine = new GregorianCalendar();
        try {
            ordine.setTime(dataP.parse(ordineStr));
        } catch (ParseException ex) {
            errori += "<li>" + ordineStr + " non &egrave; una data valida</li>";
        }

        GregorianCalendar chiusura = new GregorianCalendar();
        try {
            chiusura.setTime(dataP.parse(chiusuraStr));
        } catch (ParseException ex) {
            errori += "<li>" + chiusuraStr + " non &egrave; una data valida</li>";
        }

        GregorianCalendar today = new GregorianCalendar();

        if (daysBetween(chiusura, ordine) < 4) {
            errori += "<li>" + "la data di chiusura deve precedere quella di ordine di almeno 4 giorni</li>";
        }
        if (!today.before(chiusura)) {
            errori += "<li>" + "la data di chiusura deve essere non prima di domani</li>";
        }

        IndirizzoAbitazione indirizzoRitiro = new IndirizzoAbitazione();
        if (!controllerGMaps.isUniqueAddress(indirizzo)) {
            errori += "<li>l'indirizzo \"" + indirizzo + "\" non &egrave; stato riconosciuto</li>";
        } else {
            indirizzoRitiro.setDescrizione(indirizzo);
            String comune = DistanceMatrix.getLocality(indirizzo);
            indirizzoRitiro.setRegione(controllerComuni.getRegion(comune));
        }

        float speseSped = Float.MAX_VALUE, spesaMinima = Float.MAX_VALUE, spesaCreatore = Float.MIN_VALUE;

        if (!Regexp.checkMoney(spedizioneStr, true)) {
            errori += "<li>" + spedizioneStr + " non &egrave; un valore valido per le spese di spedizione</li>";
        } else {
            speseSped = Float.valueOf(spedizioneStr);
            if (speseSped <= 0) {
                errori += "<li>" + " la spesa di spedizione deve essere maggiore di zero</li>";
            }
        }

        boolean spedGratis = !(gratuitaStr == null) && gratuitaStr.equals("on");
        if (spedGratis) {
            if (!Regexp.checkMoney(minimaGratuitaStr, true)) {
                errori += "<li>" + spedizioneStr + " non &egrave; un valore valido per la spesa minima</li>";
            } else {
                spesaMinima = Float.valueOf(minimaGratuitaStr);
                if (spesaMinima <= 0) {
                    errori += "<li>" + " la spesa minima deve essere maggiore di zero</li>";
                }
            }
            if (!Regexp.checkMoney(spesaInizialeStr, true)) {
                errori += "<li>" + spedizioneStr + " non &egrave; un valore valido per la spesa iniziale</li>";
            } else {
                spesaCreatore = Float.valueOf(spesaInizialeStr);
                if (spesaCreatore <= 0) {
                    errori += "<li>" + " la tua spesa deve essere maggiore di zero</li>";
                }
            }
        }


        if (!"".equals(errori)) {
            throw new CampoErratoException(errori);
        }


        Annuncio annuncio = new Annuncio();
        annuncio.setDataChiusura(chiusura);
        annuncio.setDataOrdine(ordine);
        SitoAcquisti sitoInserimento = null;
        annuncio.setSpesaCreatore(spesaCreatore);
        for (SitoAcquisti sitoAcquisti : sitoAcquistiFacade.findAll()) {
            if (sito.contains(sitoAcquisti.getUrl())) {
                sitoInserimento = sitoAcquisti;
                break;
            }
        }
        if (sitoInserimento == null) {
            sitoInserimento = new SitoAcquisti();
            sitoInserimento.setUrl(sito);
            sitoAcquistiFacade.create(sitoInserimento);
        }

        indirizzoAbitazioneFacade.create(indirizzoRitiro);
        annuncio.setIndirizzoRitiro(indirizzoRitiro);
        annuncio.setCreatoreAnnuncio(creatore);
        annuncio.setSito(sitoInserimento);
        annuncio.setCostoSpedizione(speseSped);
        annuncio.setSupportaSpedGratis(spedGratis);
        annuncio.setSpesaMinSpedGratis(spesaMinima);

        annuncioFacade.create(annuncio);

        creatore.getAnnunciCreati().add(annuncio);
        utenteRegistratoFacade.edit(creatore);

    }

    private long daysBetween(GregorianCalendar startDate, GregorianCalendar endDate) {
        GregorianCalendar date = (GregorianCalendar) startDate.clone();
        long daysBetween = 0;
        while (date.before(endDate)) {
            date.add(GregorianCalendar.DAY_OF_MONTH, 1);
            daysBetween++;
        }
        return daysBetween;
    }

    /**
     * Metodo che permette al creatore di un annuncio di accettare un'adesione al proprio annuncio
     * @param a adesione da accettare
     * @param utente creatore dell'annuncio
     * @throws UtenteNonCreatoreException se l'utente che vuole effettuare l'operazione non è il creatore dell'annuncio
     */
    @Override
    public void accettaAdesione(Adesione a, UtenteRegistrato utente) throws UtenteNonCreatoreException {
        if (a.getAnnuncio().getCreatoreAnnuncio().equals(utente)) {
            a.setStato(Adesione.StatoAdesione.ACCETTATA);
            adesioneFacade.edit(a);
        } else {
            throw new UtenteNonCreatoreException("Solo il creatore dell'annuncio pu&ograve; accettare un'adesione.");
        }
    }

    /**
     * Metodo che permette al creatore di un annuncio di rifiutare un'adesione al proprio annuncio
     * @param a adesione da rifiutare
     * @param utente creatore dell'annuncio
     * @throws UtenteNonCreatoreException se l'utente che vuole effettuare l'operazione non è il creatore dell'annuncio
     */
    @Override
    public void rifiutaAdesione(Adesione a, UtenteRegistrato utente) throws UtenteNonCreatoreException {
        if (a.getAnnuncio().getCreatoreAnnuncio().equals(utente)) {
            a.setStato(Adesione.StatoAdesione.RIFIUTATA);
            adesioneFacade.edit(a);
            for (Articolo art : a.getArticoli()) {
                rifiutaArticolo(a, art, utente);
            }
        } else {
            throw new UtenteNonCreatoreException("Solo il creatore dell'annuncio pu&ograve; rifiutare un'adesione.");
        }
    }

    /**
     * Metodo che permette al creatore di un annuncio di accettare un articolo di un'adesione a un suo annuncio
     * @param a adesione al suo annuncio
     * @param art articolo da accettare
     * @param utente creatore dell'annuncio
     * @throws UtenteNonCreatoreException se l'utente che vuole svolgere l'operazione non è il creatore dell'annuncio
     */
    @Override
    public void accettaArticolo(Adesione a, Articolo art, UtenteRegistrato utente) throws UtenteNonCreatoreException {
        if (a.getAnnuncio().getCreatoreAnnuncio().equals(utente)) {
            art.setStato(Articolo.Stato.ACCETTATO);
            articoloFacade.edit(art);
        } else {
            throw new UtenteNonCreatoreException("Solo il creatore dell'annuncio pu&ograve; accettare un articolo.");
        }
    }

    /**
     * Metodo che permette al creatore di un annuncio di rifiutare un articolo di un'adesione a un suo annuncio
     * @param a adesione al suo annuncio
     * @param art articolo da rifiutare
     * @param utente creatore dell'annuncio
     * @throws UtenteNonCreatoreException se l'utente che vuole svolgere l'operazione non è il creatore dell'annuncio
     */
    @Override
    public void rifiutaArticolo(Adesione a, Articolo art, UtenteRegistrato utente) throws UtenteNonCreatoreException {
        if (a.getAnnuncio().getCreatoreAnnuncio().equals(utente)) {
            art.setStato(Articolo.Stato.RIFIUTATO);
            articoloFacade.edit(art);
        } else {
            throw new UtenteNonCreatoreException("Solo il creatore dell'annuncio pu&ograve; rifiutare un'adesione.");
        }
    }

    /**
     * Metodo per permettere al creatore di un annuncio di rimuovere il suo annuncio, a patto che non sia già chiuso
     * @param a annuncio da rimuovere
     * @param utente creatore dell'annuncio
     * @throws UtenteNonCreatoreException se l'utente che vuole svolgere l'operazione non è il creatore dell'annuncio
     * @throws AnnuncioChiusoException se l'annuncio è già chiuso
     */
    @Override
    public void rimuoviAnnuncio(Annuncio a, UtenteRegistrato utente) throws UtenteNonCreatoreException, AnnuncioChiusoException {
        if (!a.getCreatoreAnnuncio().equals(utente)) {
            throw new UtenteNonCreatoreException("Solo il creatore dell'annuncio pu&ograve; rimuovere un annuncio.");
        } else if (a.isChiuso()) {
            throw new AnnuncioChiusoException("L'annuncio &grave; gi&agrave; chiuso.");
        }

        controllerAmministrazione.cancellaAnnuncio(a);
    }

    /**
     * Metodo per permettere al creatore di un annuncio di chiudere il suo annuncio, a patto che non sia già chiuso
     * @param a annuncio da chiudere
     * @param utente creatore dell'annuncio
     * @throws UtenteNonCreatoreException se l'utente che vuole svolgere l'operazione non è il creatore dell'annuncio
     * @throws AnnuncioChiusoException se l'annuncio è già chiuso
     */
    @Override
    public void chiudiAnnuncio(Annuncio a, UtenteRegistrato utente) throws UtenteNonCreatoreException, AnnuncioChiusoException {
        if (!a.getCreatoreAnnuncio().equals(utente)) {
            throw new UtenteNonCreatoreException("Solo il creatore dell'annuncio pu&ograve; chiudere un annuncio.");
        } else if (a.isChiuso()) {
            throw new AnnuncioChiusoException("L'annuncio &grave; gi&agrave; chiuso.");
        }

        //imposto la data di chiusura ad oggi
        a.setDataChiusura(new GregorianCalendar());

        //se l'annuncio è chiuso dal creatore, tutti gli articoli e le adesioni in sospeso sono automaticamente accettate
        for (Adesione ad : a.getAdesioni()) {
            if (ad.getStato().equals(Adesione.StatoAdesione.IN_SOSPESO)) {
                ad.setStato(Adesione.StatoAdesione.RIFIUTATA);
                adesioneFacade.edit(ad);
            }

            for (Articolo art : ad.getArticoli()) {
                if (art.getStato().equals(Articolo.Stato.IN_SOSPESO)) {
                    art.setStato(Articolo.Stato.RIFIUTATO);
                    articoloFacade.edit(art);
                }
            }
        }

        annuncioFacade.edit(a);
    }

    /**
     * Metodo per permettere al creatore dell'annuncio di modificare la propria spesa per quell'ordine
     * @param utenteLoggato creatore dell'annuncio
     * @param annuncio annuncio di cui modificare la spesa
     * @param spesa nuovo valore per la spesa del creatore
     * @throws UtenteNonCreatoreException se l'utenteLoggato non è il creatore dell'annuncio
     * @throws CampoErratoException se la nuova spesa non ha un formato riconoscibile come valuta
     */
    @Override
    public void modificaSpesaCreatore(UtenteRegistrato utenteLoggato, Annuncio annuncio, String spesa) throws UtenteNonCreatoreException, CampoErratoException, AnnuncioChiusoException {
        if (utenteLoggato == null || !utenteLoggato.equals(annuncio.getCreatoreAnnuncio())) {
            throw new UtenteNonCreatoreException("Solo il creatore dell'annuncio pu&ograve; modificare la sua spesa iniziale");
        }
        if (annuncio.isChiuso()){
            throw new AnnuncioChiusoException("Non &egrave; possibile modificare la spesa iniziale di un annuncio chiuso");
        }
        if (!Regexp.checkMoney(spesa, true)) {
            throw new CampoErratoException("<li>" + spesa + " non &egrave; un importo valido. Controlla di averlo scritto nel formato corretto (ad esempio: 123.45) e senza il simbolo della valuta</li>");
        }
        float s = Float.parseFloat(spesa);
        annuncio.setSpesaCreatore(s);
        annuncioFacade.edit(annuncio);
    }

    /**
     * Persistenza
     * @param object
     */
    public void persist(Object object) {
        em.persist(object);
    }
    
}
